import asyncio
import bencoder
import os
from collections import OrderedDict
from struct import unpack, pack
from socket import inet_ntoa, inet_aton
import time
import random
from mala import get_metadata as mala_get_metadata
from bencoder import bdecode
import analysis
from ORM.Mongodb_api import BloomFilter,Mongotext
import base64
from datetime import datetime
# 获取到原信息后的步骤,首先
HANDSHAKE = 1
MESSAGE_LEN = 2
MESSAGE_TYPE = 3
MESSAGE_PAYLOAD = 4
num=0


class Maga(asyncio.DatagramProtocol):

    def __init__(self):
        self.loop = None
        self.transport = None

        self.bootstrap_nodes = (
            ("router.bittorrent.com", 6881),
            ("dht.transmissionbt.com", 6881),
            ("router.utorrent.com", 6881),
        )

        # 响应计数器(找到的节点数)
        self.find_node_an_num = 0
        # query find node的计数器（询问节点的数量）
        self.find_node_query_num = 0
        # get_peer请求计数器
        self.get_peer_query_num = 0
        # ping请求计数器
        self.get_ping_query_num = 0
        # get_peer请求个数计数器
        self.get_peer_num = 0
        self.get_announce_query_num = 0
        # send消息个数统计
        self.send_message_num = 0
        # 假的node
        self.fake_nodes = b""
        self.tid = self.gen_rand_tid()
        # 每个回传回来的node是26的长度
        self.per_node_len = 26
        # 每个nodeid长度是20
        self.per_nid_len = 20
        # 每个ip长度是4
        self.per_node_ip_len = 4
        # 每个port长度是2
        self.per_node_port_len = 2

        # 二进制的ip和端口
        self.meiguo_ip = '176.122.175.246'
        self.bali_ip = '195.154.77.110'
        self.b_ip = inet_aton(self.bali_ip)
        # 二进制的端口
        self.b_port = b""
        self.filename = 0
        #mysql存储
        self.mysql_cache = None

        #爬去到的数据
        self.data_q = asyncio.Queue()
        #测试时间
        self.start_time = datetime.now()



    def connection_made(self, transport):
        print('------------------')
        self.transport = transport

    def my_beconde_msg(self, msg):
        """
        把数据加密成beconde格式
        :param msg:
        :return:
        """
        res = bencoder.bencode(msg)
        return res

    def my_deconde_msg(self, msg):
        """
        处理返回回来encode的数据
        :param msg:
        :return:
        """
        res = bencoder.bdecode(msg)
        return res

    def datagram_received(self, data, addr):
        try:
            res = self.my_deconde_msg(data)
            # print(res)
            self.handle_recv_msg(res, addr)
        except Exception:
            pass

    def handle_recv_msg(self, msg, addr):
        """
        处理返回回来的消息
        """
        if b'y' in msg.keys():
            if msg[b'y'] == b'r':
                """
                说明是响应报文
                """
                value = msg[b'r']
                """
                查看响应的内容
                """
                if type(value) is OrderedDict:
                    """
                    判断是否是上面的类型
                    """
                    if b'nodes' in value.keys():
                        """
                        说明是find_node返回的东西
                        """
                        res = value[b'nodes']
                        # 返回的tid,返回的tid应该和发送出去的tid一致
                        res_tid = msg[b't']
                        if self.tid == res_tid:
                            """
                            交给专门解析nodes的函数去处理
                            """
                            self.handle_nodes_info(res)
                            self.find_node_an_num += 1
                    else:
                        pass
            if msg[b'y'] == b'q':
                # 获取请求的类型
                y_value = msg[b'q']
                # print(y_value)
                if y_value == b'get_peers':
                    self.handle_get_peer_msg(msg, addr)
                    self.get_peer_query_num += 1
                    # 获取info hash 和 addr ip 用这个来反向请求他。因为他很可能获取到了
                    # infohash的东西
                if y_value == b'find_node':
                    self.hand_response_find_node(msg, addr)
                    self.find_node_query_num += 1
                if y_value == b'ping':
                    """
                    ping Query = {"t":"aa", "y":"q", "q":"ping", "a":{"id":"abcdefghij0123456789"}}
                    bencoded = d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe
                    Response = {"t":"aa", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}}
                    bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re
                    返回自己的id即可
                    """
                    self.hand_ping_msg(msg, addr)
                    self.get_ping_query_num += 1
                if y_value == b"announce_peer":
                    self.handle_announce_peer(msg, addr)

    def handle_announce_peer(self, msg, addr):
        self.get_announce_query_num += 1
        t = msg[b't']
        # 0或者1 如果是1的话 使用addr里面的port 如果是0的话 使用传递过来的port
        implied_port = msg[b'a'][b'implied_port']
        port = msg[b'a'][b'port']
        token = msg[b'a'][b'token']
        info_hash = msg[b'a'][b'info_hash']
        host, port1 = addr
        # 如果这个值为0 表示要替换掉port 如果不为0就是这个port
        if implied_port:
            port = port1
        """
        # print(t)
        # print(implied_port)
        # 
        # print(port)
        # print(token)
        # print(info_hash)
        --------输出如下-----
        b'mV'
        1
        40761
        b'\x9e\xdf\xfb\xa6_\xbaf\xb7'
        b'\x9e\xdf\xfb\xa6_\xbaf\xb7\xc3B\xbf2\x19R\xa3~!P\xa5\x0c'
        """
        # 判断token是否正确
        if token != info_hash[:8]:
            print('error info(token 错误)')
        else:
            # 返回合法的回应
            msg = {
                "t": t,
                "y": "r",
                "r": {
                    "id": self.gen_rand_node_id()
                }
            }
            self.send_message(msg, addr)
            # self.loop.create_task(
            #     get_metadata_with_retries(self.loop, host, port, info_hash, self.gen_rand_node_id())
            # )
            if self.filename <= 10000:
                self.filename += 1
                print(self.filename, "线程")
                self.loop.run_until_complete(mala_get_metadata(info_hash,host,port,loop=self.loop,filename=self.filename,data_q = self.data_q))
            else:
                self.filename = 0
                print(self.filename, "线程")


    def get_neighbor(self, target, nid=None):
        """
        生成随机 target 周边节点 id，在 Kademlia 网络中，距离是通过异或(XOR)计算的，
        结果为无符号整数。distance(A, B) = |A xor B|，值越小表示越近。

        :param target: 节点 id
        """
        NEIGHBOR_END = random.randint(1, 19)
        if not nid:
            nid = self.gen_rand_id()
        return target[:NEIGHBOR_END] + nid[NEIGHBOR_END:]

    def hand_ping_msg(self, data, addr):
        """
        处理ping过来的消息
        :param msg:
        :param addr:
        :return:
        """
        t = data[b't']
        msg = data[b'a']
        res = {
            "t": t,
            "y": "r",
            "r": {
                "id": msg[b'id'],
            }
        }
        self.send_message(res, addr)

    def handle_get_peer_msg(self, msg, addr):
        """
        处理get_peers的消息
        :param msg:
        :param addr:
        :return:
        """
        infohash = msg[b"a"][b"info_hash"]
        nid = msg[b"a"][b"id"]
        token = infohash[:8]
        # print(infohash)
        msg = {
            "t": msg[b't'],
            "y": "r",
            "r": {
                "id": self.get_neighbor(infohash, nid),
                "nodes": self.fake_nodes,
                "token": token
            }
        }
        self.send_message(msg, addr)

    def hand_response_find_node(self, data, addr):
        """
        处理ping过来的消息
        :param msg:
        :param addr:
        :return:
        Example Packets
        find_node Query = {"t":"aa", "y":"q", "q":"find_node", "a": {"id":"abcdefghij0123456789", "target":"mnopqrstuvwxyz123456"}}
        bencoded = d1:ad2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e1:q9:find_node1:t2:aa1:y1:qe
        Response = {"t":"aa", "y":"r", "r": {"id":"0123456789abcdefghij", "nodes": "def456..."}}
        bencoded = d1:rd2:id20:0123456789abcdefghij5:nodes9:def456...e1:t2:aa1:y1:re
        """
        t = data[b't']
        msg = data[b'a']
        nid = msg[b'id']
        if not nid:
            nid = self.gen_rand_id()
        else:
            nid = self.get_neighbor(nid)
        # 发送消息 表示想加入节点
        # t 表示自己的id是多少
        #  a里面的 id 表示 请求的节点id是多少
        #  target表示请求的通过id的节点去找目标节点的信息
        msg = {
            "t": t,
            "y": "r",
            "r": {
                "id": nid,
                "nodes": self.fake_nodes
            }
        }
        self.send_message(msg, addr)

    def handle_nodes_info(self, nodes):
        """
        专门解析返回回来的nodes的信息
        :return:
        """
        length = len(nodes)
        PER_NID_LEN = 26
        """
        判断长度是否合法
        """
        #
        if length % 26 != 0:
            pass
        else:
            # if length > 0:
            #     self.fake_nodes = nodes
            # 伪造假装node的ip和端口号
            self.fake_nodes = b""
            for i in range(0, length, self.per_node_len):
                # 伪造ip port作为nodes

                node = nodes[i:i + self.per_node_len]
                # nid的长度是20
                nid = node[:self.per_nid_len]
                # 利用 inet_ntoa 可以返回节点 ip
                packed_ip = node[self.per_nid_len:self.per_nid_len + self.per_node_ip_len]
                ip = inet_ntoa(packed_ip)
                # 解包返回节点端口
                port = unpack("!H", node[self.per_nid_len + self.per_node_ip_len:])[0]
                # 再把拿到的数据再去请求
                msg, addr = self.send_find_node((ip, port), nid)
                tmp_fake_node = nid + self.b_ip + self.b_port
                # 有些可以伪造 有些不要伪造
                if bool(random.randint(0, 1)):
                    self.fake_nodes += tmp_fake_node
                else:
                    self.fake_nodes += node
                self.send_message(msg, addr)

    def send_msg(self, msg, addr):
        """
        发送消息函数
        :param msg:
        :param addr:
        :return:
        """
        self.transport.sendto(msg, addr)
        self.send_message_num += 1

    def send_find_node(self, address, nid=None):
        if not nid:
            nid = self.gen_rand_id()
        else:
            nid = self.get_neighbor(nid)
        # 发送消息 表示想加入节点

        # t 表示自己的id是多少
        #  a里面的 id 表示 请求的节点id是多少
        #  target表示请求的通过id的节点去找目标节点的信息
        msg = {
            "t": self.tid,
            "y": "q",
            "q": "find_node",
            "a": {
                "id": nid,
                "target": self.gen_rand_id()
            }
        }
        return msg, address

    def run(self, port=6881):
        start_time =time.time()
        print("Starting UDP server")
        self.b_port = pack('!H', port)
        self.loop = asyncio.get_event_loop()
        # One protocol instance will be created to serve all client requests
        for i in range(1):
            listen = self.loop.create_datagram_endpoint(lambda:self, local_addr=('0.0.0.0', port + i))
            transport, protocol = self.loop.run_until_complete(listen)
        self.loop.create_task(self.test_example(self.loop))
        asyncio.ensure_future(self.auto_find_nodes(), loop=self.loop)
        # asyncio.ensure_future(self.print_status(),loop=self.loop)
        # status = threading.Thread(target=self.print_status)
        # status.start()
        try:
            self.loop.run_forever()
        except KeyboardInterrupt:
            pass

        transport.close()
        self.loop.close()

    async def auto_find_nodes(self):
        for node in self.bootstrap_nodes:
            self.find_node(addr=node)

    def find_node(self, addr, node_id=None, target=None):
        if not target:
            target = self.gen_rand_node_id()

        if not node_id:
            node_id = self.gen_rand_node_id()
        self.send_message({
            "t": self.tid,
            "y": "q",
            "q": "find_node",
            "a": {
                "id": self.get_neighbor(node_id),
                "target": target
            }
        }, addr=addr)

    def send_message(self, data, addr):
        self.send_msg(self.my_beconde_msg(data), addr)

    def gen_rand_id(self, length=20):
        """
        生成随机的节点 id，长度为 20 位
        """
        random_str = os.urandom(length)
        return random_str

    def gen_rand_node_id(self):

        return self.gen_rand_id(length=20)

    def gen_rand_tid(self, length=2):

        """
        生成tid 两位即可
        """
        return self.gen_rand_id(length)

    def print_status(self):
        while 1:
            time.sleep(2)
            # print('收到响应find_node的数量{}'.format(self.find_node_an_num))
            # print('收到请求find_node数量{}'.format(self.find_node_query_num))
            print('收到请求get_peer数量{}'.format(self.get_peer_query_num))
            # print('收到请求ping数量{}'.format(self.get_ping_query_num))
            print('收到请求annouce_peer数量{}'.format(self.get_announce_query_num))
            # print('发送请求数量{}'.format(self.send_message_num))

    def parse_hash_info(self, info):
        """
        解析hashinfo
        :return:
        """
        pass

    async def test_example(self,loop):
        """
        解码存储mongo
        :param loop:
        :return:
        """
        global num
        # 实例化Maga
        # pool = await aiomysql.create_pool(host='localhost', port=3306, user='dht_text', password='zxcasd', db='dht_cache',loop=loop)
        pool = Mongotext()
        Bloom_redis =BloomFilter()
        # create default cursor 创建分离游标
        # cursor = await pool.cursor()

        # execute sql query执行sql查询
        # await cursor.execute("select * from dht_cache")

        # fetch all results 获取所有的结果
        while True:
            # 直接存储metainfo
            info_hash, data = await self.data_q.get() #拿到磁力爬取的hash和种子信息的加密字符串
            data_q_len = self.data_q.qsize()    #从对垒中获取任务
            print('队列的目前长度{}'.format(data_q_len))
            print('data_q-->get')
            # fetch_result = await cursor.fetchall()
            num += 1
            res = bdecode(base64.b64decode(data.encode())) #解码种子信息data
            print("********************************")
            print("______已找到___{}____个种子________".format(num))
            # print(res)    ##查看解码信息
            # try:
            #     file_name = (res[b'name'].decode())
            # except:
            #     continue
            # reedname = await analysis.go_torrents(file_name, res)  # 生成种子文件"reedname":reedname,

            # print(reedname)       ##查看种子名字
            msg = analysis.dht_analysis(res)        #解析各文件信息
            try:
                filename, filetime, filedict = msg

                # print(filename, filetime, filedict, reedname,info_hash,data)  ##需要存入的信息
                print(num)
                mess ={"files_name":filename,"files_time":filetime,"files_dict":filedict,"file_hash":info_hash,"file_data":data}

                # print(filedict)
                # async with pool.acquire() as conn:
                #     async with conn.cursor() as cur:
                if Bloom_redis.isContains(info_hash.encode()):  #redis去重，判断hash数值
                    print('已存在')
                else:
                    try:
                                # await cur.execute("insert into dht_cache (info_hash,value) values('{}','{}');".format(info_hash, data))

                        # await pool.insert(mess)
                        await pool.insert(mess,self.start_time,num) #进行ORM中的mongo进行存储
                        Bloom_redis.insert(info_hash.encode())#加入redis去重判断

                                # await cur.close()
                                # await conn.commit()
                    except Exception as e:
                        print('insert error :{}'.format(e))
            except TypeError: #TypeError: 'NoneType' object is not iterable
                continue


async def get_metadata_with_retries(loop, host, port, infohash, peerid):
    RETRIES = 1
    for i in range(RETRIES):
        ret = await get_metadata(loop, host, port, infohash, peerid)
        if ret:
            break


async def get_metadata(loop, host, port, infohash, peerid):
    transport, protocol = await loop.create_connection(
        lambda: BitTorrentProtocol(infohash, peerid), host, port)


class BitTorrentProtocol:
    def __init__(self, infohash, peerid):
        self.infohash = infohash
        self.peer_id = peerid

        self.protocol = 'BitTorrent protocol'
        # 协议长度
        self.protocol_len = len(self.protocol)
        # 协议的第一个字符
        self.protocol_first_char = chr(self.protocol_len)
        # 头信息结束符号
        header_end = 8
        self.header_end = header_end.to_bytes(length=8, byteorder='big')
        # 解析元信息分为几个状态 最开始需要handshake
        self.state = HANDSHAKE

        # 解析的消息
        self.field = b''
        self.leftover = b''
        self.field_len = 68

    def connection_made(self, transport):
        self.transport = transport
        msg = self.handshake_msg(self.infohash, self.peer_id)
        self.transport.write(
            msg
        )
        print('bt protocol send msg')

    def handshake_msg(self, infohash, peer_id):
        """
        组合协议内容 bep03
        :param infohash:
        :return:
        """
        # 19BitTorrent protocol
        # \x00\x00\x00\x00\x00\x10\x00\x05
        # hash
        # peer_id
        protocol_header = self.protocol_first_char + self.protocol
        protocol_header = protocol_header.encode()
        msg = protocol_header + self.header_end + infohash + peer_id
        return msg

    def data_received(self, data):
        # if self.state == MESSAGE_LEN:
        #     print('<------------------>')
        #     print(data)

        data = self.leftover + data
        # 标准返回格式是 68位 所以说 field_len的标准长度是68`
        # b'\x13BitTorrent protocol\x00\x00\x00\x00\x00\x10\x00\x05\x8e\x05\xd7\x9a\xe3\xb5\x94\xfbh\x17\xb2\x11\xc0!\xbeDd\x8e{S-XT1000-00ff540a647d'
        # 如果长度小于68 那么数据就没有解析的必要了
        if len(self.field) + len(data) < self.field_len:
            self.field += data
            self.leftover = b''
            print('1----------------')
            print(data)
            print(self.state)
        # 这个是标准长度
        elif len(self.field) + len(data) == self.field_len:
            self.field += data
            self.leftover = b''
            self.parse_data(self.field)
            # print('2----------------')
        else:
            n = self.field_len - len(self.field)
            self.field += data[:n]
            # 就和上面的self.data一样了 要对他进行分割 因为长度不是标准的
            self.leftover = data[n:]
            self.parse_data(self.field)
            # print('3----------------')

    def connection_lost(self, exc):
        self.transport.close()

    def eof_received(self):
        print('eof')

    def error_received(self, exc):
        self.transport.close()
        print('Error received:', exc)

    def parse_data(self, data):
        #如果是 握手之后的话 那么传递过来的数据各式各样
        #比如 <------------------>
        #b'\x00\x00\x00\x1b\x14\x02d8:msg_typei0e5:piecei0ee'
        #或者很长一串
        #或者是4个结束符号 \x00\x00\x00\x00

        # 如果是握手状态 那么需要检查数据格式是否正确！
        if self.state == HANDSHAKE:
            if not self.check_handshake_msg(data, self.infohash):
                self.transport.close()
                print('handshake信息校验不成功')
                return
            self.state = MESSAGE_LEN

            # 发送扩展信息
            extended_handshake = bencoder.bencode({
                'm': {b'ut_metadata': 2},
                'v': 'S.P.E.W.'
            })
            self.write_extended_message(0, extended_handshake)

        elif self.state == MESSAGE_LEN:
            self.message_len = int.from_bytes(self.field, byteorder='big')
            if self.message_len == 0:
                self.state = MESSAGE_LEN
                self.field = 4
            else:
                self.state = MESSAGE_TYPE
                self.field_len = 1
        elif self.state == MESSAGE_TYPE:
            self.message_type = int.from_bytes(self.field, byteorder='big')
            if self.message_len == 1:
                self.state = MESSAGE_LEN
                self.field = 4
            else:
                self.message_len -= 1
                self.field_len = self.message_len
                self.state = MESSAGE_PAYLOAD
        elif self.state == MESSAGE_PAYLOAD:
            self.parse_message()
            self.field_len = 4
            self.state = MESSAGE_LEN

        self.field = b''

    def write_extended_message(self, ex_type, msg):
        self.write_message(20, bytes([ex_type]) + msg)

    def write_message(self, type, msg):
        msg_len = 1 + len(msg)
        self.transport.write(msg_len.to_bytes(length=4, byteorder='big') + \
                             bytes([type]) + msg)

    def parse_message(self):

        if self.message_type == 20:
            print('11111111111111111111')

    def check_handshake_msg(self, msg, infohash):
        """
        检查返回回来的数据是否正确,主要是分头信息和infohash是否一致
        :param msg:
        :param infohash:
        :return:
        """
        header_first_char = msg[0]
        header_protocol = msg[1:20]
        res_infohash = msg[28:48]
        if header_first_char != self.protocol_len:
            print('第一次握手后的校验头第一个字失败')
            return False

        if int.from_bytes(msg[20:28], byteorder='big') & 0x0000000000100000 == 0:
            print('Peer does not support extension protocol.')
            return False

        if int.from_bytes(msg[20:28], byteorder='big') & 0x0000000000000004 == 0:
            print('Peer does not support fast protocol.')
            return False

        if header_protocol != self.protocol.encode():
            print('第一次握手后的校验协议失败')
            return False
        # 鉴定infohash是否一致
        if res_infohash != infohash:
            print('第一次握手后的校验infohsah失败')
            return False
        return True





def start(port):
    e = Maga()
    e.run(port)


if __name__ == '__main__':

    start(6881)


